#include "TemplateCompiler.h"
#include "Template.h"
#include "Phnqlet.h"
#include "PhnqletManager.h"
#include "TagManager.h"
#include "xml_util.h"
#include "UTH.h"
#include <sstream>

namespace phnq
{
	namespace phnqlets
	{
		struct compile_info
		{
			const char *topClassName;
			Phnqlet *ctxPhnqlet;
			stringbuf compilebuf;
			stringbuf outbuf;
			int bodyFnIdx;
			int bodyFnBodyIdx;
			int startTagIdx;
			string phnqletType;
			map<string,string> phnqletAttrs;
			struct
			{
				set<Tag*> tags;
				set<Phnqlet*> phnqlets;
			} deps;
		};
	
		string trim(string s)
		{
			int i;
		
			for(i=0; i<s.length(); i++)
			{
				char c = s[i];
				if(c != ' ' && c != '\n' && c != '\r' && c != '\t')
					break;
			}
			s = s.substr(i);
		
			i = s.length();
			while(i--)
			{
				char c = s[i];
				if(c != ' ' && c != '\n' && c != '\r' && c != '\t')
					break;
			}
			s = s.substr(0, i+1);
		
			return s;
		}

		string escapeJS(string str)
		{
			stringbuf buf;
		
			for(int i=0; i<str.length(); i++)
			{
				char c = str[i];
				switch(c)
				{
					case '\\':
						buf.sputc('\\');
						buf.sputc('\\');
						break;

					case '\"':
						buf.sputc('\\');
						buf.sputc('\"');
						break;

					case '\n':
						buf.sputc('\\');
						buf.sputc('n');
						break;
					
					default:
						buf.sputc(c);
				}
			}
		
			return buf.str();
		}
	
		string compileExpressions(string str, bool &singleExp)
		{
			stringbuf buf;
			int numExps = 0;
			int level = 0;
			bool und = false;
			for(int i=0; i<str.length(); i++)
			{
				char c = str[i];
			
				if(c == '_')
				{
					und = true;
					c = str[++i];
				}
			
				if(c == '{')
				{
					if(level == 0 && und)
					{
						numExps++;
						buf.sputn("\"+(", 3);
						level = 1;
					}
					else
					{
						if(level > 0)
							level ++;
					
						buf.sputc('{');
					}
				}
				else if(c =='}')
				{
					if(level > 0)
					{
						level--;
						if(level == 0)
						{
							buf.sputn(")+\"", 3);
						}
						else
						{
							buf.sputc('}');
						}
					}
					else
					{
						buf.sputc('}');
					}
				}
				else
				{
					if(und)
						buf.sputc('_');
					
					buf.sputc(c);
				}
				und = false;
			}
		
			string comp = buf.str();
			int len = comp.length();
		
			if(numExps == 1 && comp[0]=='\"' && comp[1]=='+' && comp[len-2]=='+' && comp[len-1]=='\"')
			{
				singleExp = true;
				comp = comp.substr(2, comp.length()-4);
			}
			else
			{
				singleExp = false;
			}	
		
			return comp;
		}
	
		void flushOutBuff(compile_info *info)
		{
			string outstr = info->outbuf.str();
			if(outstr.length() == 0)
				return;
		
			bool singleExp;
			string compiledStr = compileExpressions(escapeJS(outstr), singleExp);
			info->outbuf.str("");
		
			info->compilebuf.sputn("\n_(", 3);
			if(!singleExp)
			{
				info->compilebuf.sputc('\"');
			}
			info->compilebuf.sputn(compiledStr.c_str(), compiledStr.length());
			if(!singleExp)
			{
				info->compilebuf.sputc('\"');
			}
			info->compilebuf.sputn(");", 2);
		}
	
		void out(compile_info *info, string s)
		{
			s = trim(s);
		
			if(s.length())
			{
				info->outbuf.sputn(s.c_str(), s.length());
			}
		}
		
		string unwrapAttrs(map<string,string> attrsMap)
		{
			stringbuf attrsBuf;
			for(map<string,string>::iterator it=attrsMap.begin(); it != attrsMap.end(); it++)
			{
				string n = it->first;
				string v = it->second;
				attrsBuf.sputc(' ');
				attrsBuf.sputn(n.c_str(), n.length());
				attrsBuf.sputc('=');
				attrsBuf.sputc('"');
				attrsBuf.sputn(v.c_str(), v.length());
				attrsBuf.sputc('"');
			}
			return attrsBuf.str();
		}
	
		static void XMLCALL startTag(void *userData, const char *name, const char **attrs)
		{
			compile_info *info = (compile_info*)userData;

			map<string,string> attrsMap = xml_util_getattributesmap(attrs);
		
			Tag *tag = TagManager::getInstance()->get(name);
			if(tag)
			{
				if(strcmp(name, "org.phnq.phnqlets.phnqlet") == 0)
				{
					string lazy = attrsMap["lazy"];
					attrsMap.erase("lazy");
				
					if(lazy == "true")
					{
						string classAttr = attrsMap["class"];
						attrsMap["class"] = classAttr == "" ? "phnqlet_ph" : "phnqlet_ph " + classAttr;
						string phpStartTag = "<ul "+unwrapAttrs(attrsMap)+">";
						info->phnqletType = "lazy";
						out(info, phpStartTag);
					}
					else
					{
						string type = attrsMap["type"];
						attrsMap.erase("type");

						// string classAttr = attrsMap["class"];
						// bool singleExp;
						// attrsMap["class"] = compileExpressions(classAttr == "" ? "_{attrs['class']||''}" : "_{attrs['class']||''} " + classAttr, singleExp);

						Phnqlet *p = PhnqletManager::getInstance()->get(type);
						if(p)
						{
							info->deps.phnqlets.insert(p);
						}
						info->phnqletType = type;
						info->phnqletAttrs = attrsMap;
					}
				}
			
				info->deps.tags.insert(tag);
				
				set<string> tagDeps = tag->getDependencies();
				for(set<string>::iterator it=tagDeps.begin(); it!=tagDeps.end(); it++)
				{
					Phnqlet *p = PhnqletManager::getInstance()->get(*it);
					if(p)
						info->deps.phnqlets.insert(p);
				}
			
				flushOutBuff(info);

				info->compilebuf.sputn("\n_t(", 4);
			
				char indexStr[8];
				sprintf(indexStr, "%i", tag->getIndex());
				info->compilebuf.sputn(indexStr, strlen(indexStr));
			
				info->compilebuf.sputn(",{", 2);
			
				int i = 0;
				for(map<string,string>::iterator it=attrsMap.begin(); it != attrsMap.end(); it++)
				{
					string n = it->first;
					string v = it->second;

					if(i++)
						info->compilebuf.sputc(',');
				
					bool singleExp;
					string comp = compileExpressions(v.c_str(), singleExp);
					info->compilebuf.sputc('_');
					info->compilebuf.sputn(n.c_str(), n.length());
					info->compilebuf.sputc(':');
					if(!singleExp)
					{
						info->compilebuf.sputc('\"');
					}
					info->compilebuf.sputn(comp.c_str(), comp.length());
					if(!singleExp)
					{
						info->compilebuf.sputc('\"');
					}
				}
				info->compilebuf.sputc('}');
			
				info->compilebuf.sputn(",_tag,_,", 8);
				
				char ptypeIdxChars[10];
				sprintf(ptypeIdxChars, "%i", info->ctxPhnqlet->getIndex());
				info->compilebuf.sputn(ptypeIdxChars, strlen(ptypeIdxChars));
			
				info->bodyFnIdx = info->compilebuf.str().length();
			
				const char *s = ",function(){eval(_expArgs(arguments));";
				info->compilebuf.sputn(s, strlen(s));
			
				info->bodyFnBodyIdx = info->compilebuf.str().length();
			}
			else
			{
				stringbuf buf;
			
				buf.sputc('<');
				buf.sputn(name, strlen(name));
			
				for(map<string,string>::iterator it=attrsMap.begin(); it != attrsMap.end(); it++)
				{
					string n = it->first;
					string v = it->second;
				
					buf.sputc(' ');
					buf.sputn(n.c_str(), n.length());
					buf.sputn("=\"", 2);
					if(info->topClassName && n == "class")
					{
						buf.sputn(info->topClassName, strlen(info->topClassName));
						buf.sputc(' ');
						info->topClassName = NULL;
					}
				
					int vlen = v.length();
					if(info->ctxPhnqlet && strcmp(name, "img")==0 && n=="src" && vlen>0 && v[0] != '/')
					{
						string relPath = info->ctxPhnqlet->getRelativePath(v);
						buf.sputn(relPath.c_str(), relPath.length());
					}
					else
					{
						buf.sputn(v.c_str(), vlen);
					}
				
					buf.sputc('\"');
				}
			
				if(info->topClassName)
				{
					buf.sputc(' ');
					buf.sputn("class", 5);
					buf.sputn("=\"", 2);
					buf.sputn(info->topClassName, strlen(info->topClassName));
					buf.sputc('\"');
					info->topClassName = NULL;
				}
			
				buf.sputc('>');
			
				string s = buf.str();
				out(info, s);
		
				info->startTagIdx = info->outbuf.str().length();
			}
		}

		static void XMLCALL endTag(void *userData, const char *name)
		{
			compile_info *info = (compile_info*)userData;
		
			Tag *tag = TagManager::getInstance()->get(name);
			if(tag)
			{
				flushOutBuff(info);
			
				bool isPhnqletTag = strcmp(name, "org.phnq.phnqlets.phnqlet") == 0;
				if(isPhnqletTag)
				{
					if(info->phnqletType == "lazy")
					{
						out(info, "</ul>");
					}
					else
					{
						Phnqlet *p = PhnqletManager::getInstance()->get(info->phnqletType);
						Template *t = p->getTemplate("main");
						if(t)
						{
							UTHObject obj;
							map<string,string> attrsMap = info->phnqletAttrs;
							for(map<string,string>::iterator it=attrsMap.begin(); it != attrsMap.end(); it++)
							{
								string n = it->first;
								string v = it->second;
								obj.set(n, v);
							}
							string attrsObjStr = obj.toString();
							
							char c[32+attrsObjStr.length()];
							sprintf(c, "templates[%i](_tag.params,%s);", t->getIndex(), attrsObjStr.c_str());
							info->compilebuf.sputn(c, strlen(c));
						}
					}
				}
					
				string s = info->compilebuf.str();
				if(info->bodyFnBodyIdx == s.length() && !isPhnqletTag)
				{
					s = s.substr(0, info->bodyFnIdx);
					info->compilebuf.str("");
					info->compilebuf.sputn(s.c_str(), s.length());
				}
				else
				{
					info->compilebuf.sputc('}');
				}
			
				info->compilebuf.sputn(");", 2);
			}
			else
			{
				string s = info->outbuf.str();
			
				bool noEmptyTag = strcmp(name, "script") == 0 || strcmp(name, "div") == 0 || strcmp(name, "a") == 0;

				if(!noEmptyTag && info->startTagIdx == s.length())
				{
					info->outbuf.str("");
					s = s.substr(0, s.length()-1);
					info->outbuf.sputn(s.c_str(), s.length());
					info->outbuf.sputn("/>", 2);
				}
				else
				{
					char c[80];
					sprintf(c, "</%s>", name);
					out(info, c);
				}
			}
		}
	
		static void XMLCALL charData(void *userData, const XML_Char *s, int len)
		{
			compile_info *info = (compile_info*)userData;
			char c[len+1];
			strncpy(c, s, len);
			c[len] = '\0';
			out(info, c);
		}
	
		compile_result TemplateCompiler::compile(const char *filename, const char *topClassName, Phnqlet *ctxPhnqlet)
		{
			compile_info info;
			info.topClassName = topClassName;
			info.ctxPhnqlet = ctxPhnqlet;
			info.deps.phnqlets = ctxPhnqlet->getExplicitDependencies();
			
			char i18nDecl[256];
			sprintf(i18nDecl, "var i18n = new i18ns[%i]();var ptype=ptypes[%i];", ctxPhnqlet->getIndex(), ctxPhnqlet->getIndex());
			info.compilebuf.sputn(i18nDecl, strlen(i18nDecl));
			
			xml_util_parse_sax(filename, &info, startTag, endTag, charData);
			flushOutBuff(&info);

			compile_result result;
			result.objectCode = trim(info.compilebuf.str());
			result.deps.tags = info.deps.tags;
			result.deps.phnqlets = info.deps.phnqlets;
			
			return result;
		}
	}
}
